"""
gui.py - mysqldiff 图形界面 (GUI)
功能：通过 PySimpleGUI 构建表单，调用 mysqldiff.py 执行数据库对比
依赖：PySimpleGUI, Python 内置 subprocess, os, tkinter
运行前请确保：mysqldiff.py 与 gui.py 在同一目录，或使用完整路径
"""

import PySimpleGUI as sg
import subprocess
import sys
import os
import re  # 用于清理字符串
import traceback
import threading

# 全局变量：跟踪最后生成的SQL文件路径
last_generated_sql_file = None


def copy_to_clipboard(text, parent_element=None):
    """复制文本到剪贴板，并确保提示消息显示在正确的GUI层级上
    
    Args:
        text: 要复制的文本
        parent_element: 可选的父元素（如Window），用于定位弹窗
    """
    try:
        import pyperclip
        pyperclip.copy(text)
        # 使用 parent_element 确保消息显示在正确层级
        sg.popup_quick_message(' 已复制到剪贴板', 
                             auto_close=True, 
                             auto_close_duration=1,
                             non_blocking=True,
                             grab_anywhere=False,
                             keep_on_top=True)
    except ImportError:
        try:
            import tkinter as tk
            r = tk.Tk()
            r.withdraw()
            r.clipboard_clear()
            r.clipboard_append(text)
            r.update()
            r.destroy()
            sg.popup_quick_message(' 已复制到剪贴板', 
                                 auto_close=True, 
                                 auto_close_duration=1,
                                 non_blocking=True,
                                 keep_on_top=True)
        except Exception as e:
            sg.popup_error(f"复制到剪贴板失败: {e}\n请安装 'pyperclip' 模块 (pip install pyperclip)",
                         keep_on_top=True)


def get_script_dir():
    """获取脚本或 exe 文件所在的目录"""
    if getattr(sys, 'frozen', False):
        return os.path.dirname(sys.executable)
    else:
        return os.path.dirname(os.path.abspath(__file__))


def clean_input_string(input_str):
    """清理输入字符串，移除首尾空白和潜在的不可见字符"""
    if not isinstance(input_str, str):
        return str(input_str)
    # 移除首尾空白字符（包括 \n, \r, \t, 空格）
    cleaned = input_str.strip()
    # 移除中间可能的换行符或其他控制字符（保留路径分隔符）
    # 这里简单地移除所有非打印字符，但保留常见的路径字符
    # 更严格的正则可以根据需要调整
    cleaned = re.sub(r'[\x00-\x1f\x7f-\x9f]', '', cleaned)
    return cleaned


def get_full_outfile_path(outfile_input_value):
    """根据输入值和脚本目录计算完整输出路径"""
    # 清理输入
    outfile_input_value = clean_input_string(outfile_input_value)

    if os.path.isabs(outfile_input_value):
        return outfile_input_value
    else:
        script_dir = get_script_dir()
        # 确保使用正斜杠，避免Windows路径问题
        full_path = os.path.join(script_dir, outfile_input_value)
        return full_path.replace('\\', '/')


def get_mysqldiff_path():
    """获取mysqldiff.py文件的路径"""
    if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
        # 在PyInstaller打包环境中
        # mysqldiff.py作为数据文件被打包进exe中，需要从_MEIPASS目录获取
        mysqldiff_py = os.path.join(sys._MEIPASS, 'mysqldiff.py')
        if os.path.exists(mysqldiff_py):
            return mysqldiff_py
        else:
            # 如果在_MEIPASS中找不到，尝试在exe同目录下查找
            exe_dir = os.path.dirname(sys.executable)
            mysqldiff_py = os.path.join(exe_dir, 'mysqldiff.py')
            if os.path.exists(mysqldiff_py):
                return mysqldiff_py
    else:
        # 非打包环境
        base_path = os.path.dirname(os.path.abspath(__file__))
        mysqldiff_py = os.path.join(base_path, 'mysqldiff.py')
        if os.path.exists(mysqldiff_py):
            return mysqldiff_py
    
    return None


def is_valid_connection_string(conn_str):
    """验证连接字符串格式是否正确 (user:pass@host:port)"""
    if not conn_str or not isinstance(conn_str, str):
        return False
    
    # 基本格式检查：必须包含 : 和 @ 字符
    if ':' not in conn_str or '@' not in conn_str:
        return False
    
    # 分割连接字符串
    try:
        # user:pass@host:port
        user_pass, host_port = conn_str.split('@')
        user, passwd = user_pass.split(':', 1)
        host, port = host_port.split(':', 1)
        
        # 简单验证各部分不为空
        if not user or not passwd or not host or not port:
            return False
            
        # 验证端口号是否为数字
        if not port.isdigit():
            return False
            
        return True
    except ValueError:
        return False


def main():
    # 声明全局变量，用于跟踪最后生成的SQL文件
    global last_generated_sql_file
    
    # --- 兼容旧版 PySimpleGUI ---
    # sg.theme('LightBlue2') # 新版用法
    sg.ChangeLookAndFeel('LightBlue2')  # 旧版兼容用法
    # --- 兼容结束 ---

    layout = [
        [sg.Text('mysqldiff - MySQL 数据库结构对比工具', font=('Microsoft YaHei', 14, 'bold'))],
        [sg.Text('_' * 60)],
        [sg.Text('新数据库:', size=(12, 1)), sg.Input('db_new', key='-NEW_DB-', size=(15, 1)),
         sg.Text('旧数据库:', size=(12, 1)), sg.Input('db_old', key='-OLD_DB-', size=(15, 1))],
        [sg.Checkbox('使用独立连接（高级）', key='-INDEPENDENT_CONN-', default=False, enable_events=True)],
        [sg.Frame('共用连接', [
            [sg.Text('连接字符串:', size=(12, 1)),
             sg.Input('root:root@localhost:3306', key='-CONN-', size=(40, 1))]
        ], key='-FRAME_COMMON-', visible=True)],
        [sg.Frame('独立连接', [
            [sg.Text('新库连接:', size=(12, 1)),
             sg.Input('root:newpass@192.168.1.10:3306', key='-CONN_NEW-', size=(40, 1))],
            [sg.Text('旧库连接:', size=(12, 1)),
             sg.Input('backup:123@192.168.1.20:3306', key='-CONN_OLD-', size=(40, 1))]
        ], key='-FRAME_INDEPENDENT-', visible=False)],
        [sg.Text('输出文件位置:', size=(12, 1)),
         sg.Input('diff.sql', key='-OUTFILE-', size=(30, 1)),
         sg.FileSaveAs(file_types=(("SQL Files", "*.sql"), ("All Files", "*.*")))],
        [sg.Checkbox('跳过外键 (--no-fk)', key='-NO_FK-', default=True)],
        [sg.Checkbox('立即执行 (-x)', key='-EXEC-', default=False)],
        [sg.Checkbox('拷贝数据 (-c)', key='-COPY-', default=False,
                     tooltip='与 -x 一起使用时，执行 INSERT 数据迁移')],
        [sg.Text(''),
         sg.Button('生成 SQL', key='-RUN-'),
         sg.Button('查看 SQL', key='-VIEW-'),
         sg.Button('保存 SQL', key='-SAVE-'),
         sg.Button('详细日志', key='-TOGGLE-LOG-', button_color=('white', 'gray')),
         sg.Button('清除', key='-CLEAR-'),
         sg.Button('退出', key='-EXIT-')
         ],
        [sg.Text('运行状态:', font=('Microsoft YaHei', 9, 'underline'))],
        [sg.Multiline('', size=(70, 10), key='-OUTPUT-', autoscroll=True, disabled=True)],
        # 添加状态指示器
        [sg.Text('', key='-STATUS-', visible=False, text_color='blue')]
    ]

    window = sg.Window('mysqldiff GUI', layout, finalize=True)
    window.refresh()  # 立即刷新窗口以提高响应性
    
    # 初始化时也计算一次完整路径
    sql_file = get_full_outfile_path(window['-OUTFILE-'].get())

    # 添加一个变量来跟踪日志显示模式
    show_detailed_log = False

    while True:
        event, values = window.read(timeout=100)  # 添加超时以允许处理队列事件

        if event in (sg.WIN_CLOSED, '-EXIT-'):
            break

        if event == '-INDEPENDENT_CONN-':
            use_independent = values['-INDEPENDENT_CONN-']
            window['-FRAME_COMMON-'].update(visible=not use_independent)
            window['-FRAME_INDEPENDENT-'].update(visible=use_independent)
            window['-OUTPUT-'].update(f"[UI] 切换为 {'独立连接' if use_independent else '共用连接'} 模式\n",
                                      append=True)
            window.refresh()  # 立即刷新界面
            continue

        # 处理详细日志显示切换
        if event == '-TOGGLE-LOG-':
            show_detailed_log = not show_detailed_log
            window['-TOGGLE-LOG-'].update(button_color=('white', 'green' if show_detailed_log else 'gray'))
            window['-TOGGLE-LOG-'].update(text='简要日志' if show_detailed_log else '详细日志')
            window['-OUTPUT-'].update(f"[UI] 日志显示模式切换为: {'详细' if show_detailed_log else '简要'}\n", append=True)
            continue

        if event == '-CLEAR-':
            window['-OUTPUT-'].update('')
            window['-NEW_DB-'].update('db_new')
            window['-OLD_DB-'].update('db_old')
            window['-CONN-'].update('root:root@localhost:3306')
            window['-CONN_NEW-'].update('root:newpass@192.168.1.10:3306')
            window['-CONN_OLD-'].update('backup:123@192.168.1.20:3306')
            window['-OUTFILE-'].update('diff.sql')
            window['-NO_FK-'].update(True)
            window['-EXEC-'].update(False)
            window['-COPY-'].update(False)
            window['-INDEPENDENT_CONN-'].update(False)
            window['-FRAME_COMMON-'].update(visible=True)
            window['-FRAME_INDEPENDENT-'].update(visible=False)
            sql_file = get_full_outfile_path('diff.sql')
            continue

        if event == '-RUN-':
            # 禁用所有按钮以防止重复点击
            buttons = ['-RUN-', '-VIEW-', '-SAVE-', '-CLEAR-', '-EXIT-']
            for button in buttons:
                window[button].update(disabled=True)
            
            # 立即刷新界面
            window.refresh()
            
            # --- 读取并清理用户输入 ---
            new_db = clean_input_string(values['-NEW_DB-'])
            old_db = clean_input_string(values['-OLD_DB-'])
            outfile_name = clean_input_string(values['-OUTFILE-'])
            no_fk = values['-NO_FK-']
            exec_sql = values['-EXEC-']
            copy_data = values['-COPY-']
            use_independent = values['-INDEPENDENT_CONN-']

            if not new_db or not old_db or not outfile_name:
                sg.popup_error('请填写数据库名和输出文件名！')
                # 重新启用按钮
                for button in buttons:
                    window[button].update(disabled=False)
                window.refresh()
                continue

            # 在开始处理前显示进度信息
            window['-OUTPUT-'].update('[进度] 正在准备处理请求...\n', append=True)
            window.refresh()  # 立即刷新界面显示进度
            
            # --- 确定 mysqldiff.py 路径 ---
            mysqldiff_py = get_mysqldiff_path()
            
            if not mysqldiff_py or not os.path.exists(mysqldiff_py):
                sg.popup_error(f"错误：未找到 mysqldiff.py\n路径：{mysqldiff_py}")
                # 重新启用按钮
                for button in buttons:
                    window[button].update(disabled=False)
                window.refresh()
                continue

            window['-OUTPUT-'].update(f'[调试] mysqldiff.py 路径: {mysqldiff_py}\n', append=True)

            # --- 关键调整：确定并使用绝对输出文件路径 ---
            # 统一使用脚本目录作为输出目录，确保在打包和非打包环境中都能正确访问文件
            script_dir = get_script_dir()
            if os.path.isabs(outfile_name):
                # 如果用户指定了绝对路径，检查是否在脚本目录下
                if outfile_name.startswith(script_dir.replace('\\', '/').replace('\\', '/')):
                    full_outfile_path_abs = outfile_name
                else:
                    # 如果不在脚本目录下，将其调整到脚本目录
                    filename = os.path.basename(outfile_name)
                    full_outfile_path_abs = os.path.join(script_dir, filename).replace('\\', '/')
            else:
                # 使用相对路径时，默认放在脚本目录
                full_outfile_path_abs = os.path.join(script_dir, outfile_name).replace('\\', '/')
            
            output_dir = os.path.dirname(full_outfile_path_abs)
            sql_file = full_outfile_path_abs  # 更新全局变量供后续使用
            
            # 更新全局变量，供view功能使用
            last_generated_sql_file = full_outfile_path_abs
            
            window['-OUTPUT-'].update(f'[调试] 使用脚本目录作为输出路径\n', append=True)
            window['-OUTPUT-'].update(f'[调试] 脚本目录: {script_dir}\n', append=True)
            
            # 确保输出文件的目录存在
            os.makedirs(output_dir, exist_ok=True)

            window['-OUTPUT-'].update(f'[调试] 绝对输出路径: {full_outfile_path_abs}\n', append=True)
            window['-OUTPUT-'].update(f'[调试] 输出目录: {output_dir}\n', append=True)
            window['-OUTPUT-'].update(f'[调试] 当前工作目录: {os.getcwd()}\n', append=True)
            if getattr(sys, 'frozen', False):
                window['-OUTPUT-'].update(f'[调试] EXE所在目录: {get_script_dir()}\n', append=True)
                if hasattr(sys, '_MEIPASS'):
                    window['-OUTPUT-'].update(f'[调试] 临时目录(_MEIPASS): {getattr(sys, "_MEIPASS", "N/A")}\n', append=True)

            # --- 构建命令 (改进方式) ---
            # 直接调用脚本文件，而非 -c exec(...)
            # 在Windows上，使用python.exe而不是pythonw.exe以确保输出能被捕获
            python_executable = sys.executable
            window['-OUTPUT-'].update(f'[调试] 初始python解释器: {python_executable}\n', append=True)
            window['-OUTPUT-'].update(f'[调试] 是否在打包环境中: {getattr(sys, "frozen", False)}\n', append=True)
            
            # 在打包环境中，使用系统python解释器
            if getattr(sys, 'frozen', False):
                window['-OUTPUT-'].update(f'[调试] 在打包环境中，使用系统python解释器\n', append=True)
                
                # 尝试通过where命令查找python解释器
                try:
                    result = subprocess.run(['where', 'python'], capture_output=True, text=True, shell=True, timeout=10)
                    if result.returncode == 0 and result.stdout:
                        stdout_content = result.stdout.strip()
                        python_path = stdout_content.split('\n')[0] if stdout_content else ""
                        if python_path and os.path.exists(python_path):
                            python_executable = python_path
                            window['-OUTPUT-'].update(f'[调试] 通过where命令找到python: {python_executable}\n', append=True)
                        else:
                            window['-OUTPUT-'].update(f'[调试] where命令找到的python路径无效: {python_path}\n', append=True)
                    else:
                        window['-OUTPUT-'].update(f'[调试] where命令未找到python，使用默认python\n', append=True)
                except Exception as e:
                    window['-OUTPUT-'].update(f'[调试] where命令执行失败: {e}\n', append=True)
            
            window['-OUTPUT-'].update(f'[调试] 最终选择的python解释器: {python_executable}\n', append=True)
            
            cmd = [python_executable, mysqldiff_py]

            if use_independent:
                # 独立连接模式下添加进度提示
                window['-OUTPUT-'].update('[进度] 正在处理独立连接参数...\n', append=True)
                window.refresh()  # 立即刷新界面显示进度
                
                conn_new = clean_input_string(values['-CONN_NEW-'])
                conn_old = clean_input_string(values['-CONN_OLD-'])
                if not conn_new or not conn_old:
                    sg.popup_error('请填写新库和旧库的连接字符串！')
                    # 重新启用按钮
                    for button in buttons:
                        window[button].update(disabled=False)
                    window.refresh()
                    continue
                    
                # 添加连接字符串基本格式验证
                if not is_valid_connection_string(conn_new):
                    sg.popup_error(f'新库连接字符串格式不正确: {conn_new}\n格式应为 user:pass@host:port')
                    # 重新启用按钮
                    for button in buttons:
                        window[button].update(disabled=False)
                    window.refresh()
                    continue
                    
                if not is_valid_connection_string(conn_old):
                    sg.popup_error(f'旧库连接字符串格式不正确: {conn_old}\n格式应为 user:pass@host:port')
                    # 重新启用按钮
                    for button in buttons:
                        window[button].update(disabled=False)
                    window.refresh()
                    continue
                    
                cmd.extend([f'--sn={conn_new}', f'--so={conn_old}'])
            else:
                conn = clean_input_string(values['-CONN-'])
                if not conn:
                    sg.popup_error('请填写共用连接字符串！')
                    # 重新启用按钮
                    for button in buttons:
                        window[button].update(disabled=False)
                    window.refresh()
                    continue
                    
                # 添加连接字符串基本格式验证
                if not is_valid_connection_string(conn):
                    sg.popup_error(f'共用连接字符串格式不正确: {conn}\n格式应为 user:pass@host:port')
                    # 重新启用按钮
                    for button in buttons:
                        window[button].update(disabled=False)
                    window.refresh()
                    continue
                    
                cmd.append(f'--s={conn}')

            # 传递 绝对路径 给 --file 参数，使用系统原生路径格式
            # 确保路径使用正斜杠，避免Windows路径问题
            normalized_path = full_outfile_path_abs.replace('\\', '/')
            file_arg = '--file=' + normalized_path
            cmd.append(file_arg)
            
            # 添加 --no-redirect 参数，防止在GUI模式下重定向输出到日志文件
            cmd.append('--no-redirect')
            
            # 不再预先创建文件，让mysqldiff.py自己创建
            window['-OUTPUT-'].update(f'[调试] 输出文件路径: {full_outfile_path_abs}\n', append=True)
            
            # 如果文件已存在，先删除它
            if os.path.exists(full_outfile_path_abs):
                try:
                    os.remove(full_outfile_path_abs)
                    window['-OUTPUT-'].update(f'[调试] 删除已存在的输出文件\n', append=True)
                except Exception as e:
                    window['-OUTPUT-'].update(f'[警告] 删除已存在文件失败: {e}\n', append=True)
            if no_fk:
                cmd.append('--no-fk')
            if exec_sql:
                cmd.append('-x')
            # 修改：使拷贝数据功能不依赖于立即执行选项
            if copy_data:
                cmd.append('-c')

            cmd.append(f'{new_db}:{old_db}')

            window['-OUTPUT-'].update(f'运行命令:\n{" ".join(cmd)}\n\n')
            window.refresh()  # 立即刷新界面显示命令

            # --- 异步执行命令 ---
            def run_command_async(cmd, script_working_dir, show_detailed_log, progress_queue):
                """异步执行命令的函数，支持进度更新"""
                try:
                    # Windows特定设置：防止创建新控制台窗口
                    startupinfo = None
                    creationflags = 0
                    if sys.platform == 'win32':
                        startupinfo = subprocess.STARTUPINFO()
                        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
                        # 更彻底地防止创建新窗口
                        startupinfo.wShowWindow = subprocess.SW_HIDE
                        # 添加创建标志，防止创建控制台窗口
                        creationflags = subprocess.CREATE_NO_WINDOW
                        
                        # 在打包环境中，使用更完整的参数组合来隐藏窗口
                        if getattr(sys, 'frozen', False):
                            # 使用DETACHED_PROCESS标志确保在打包环境中也能正确隐藏窗口
                            creationflags |= subprocess.DETACHED_PROCESS

                    # 执行命令，使用errors='ignore'来避免编码问题
                    result = subprocess.run(
                        cmd,
                        capture_output=True,
                        text=True,
                        encoding='utf-8',
                        errors='ignore',  # 忽略编码错误
                        cwd=script_working_dir,
                        stdin=subprocess.DEVNULL,
                        startupinfo=startupinfo,
                        creationflags=creationflags,
                        timeout=300  # 增加超时时间到5分钟
                    )
                    return result
                except Exception as e:
                    return e

            # 执行命令前显示进度信息
            window['-OUTPUT-'].update('[进度] 正在执行mysqldiff命令，请稍候...\n', append=True)
            window.refresh()  # 立即刷新界面显示进度
            
            # 设置工作目录为输出文件所在的目录
            # 这样可以确保如果 mysqldiff.py 内部依赖 cwd，也能正常工作
            script_working_dir = output_dir

            # 只在详细模式下显示调试信息
            if show_detailed_log:
                window['-OUTPUT-'].update(f'[调试] subprocess 工作目录: {script_working_dir}\n', append=True)
                window['-OUTPUT-'].update(f'[调试] 工作目录是否存在: {os.path.exists(script_working_dir)}\n', append=True)
                window['-OUTPUT-'].update(f'[调试] 工作目录是否可写: {os.access(script_working_dir, os.W_OK)}\n', append=True)

            # 只在详细模式下显示命令和文件路径调试信息
            if show_detailed_log:
                window['-OUTPUT-'].update(f'[调试] 完整命令: {" ".join(cmd)}\n', append=True)
                
                # 检查mysqldiff.py文件是否存在
                window['-OUTPUT-'].update(f'[调试] mysqldiff.py路径: {mysqldiff_py}\n', append=True)
                window['-OUTPUT-'].update(f'[调试] mysqldiff.py是否存在: {os.path.exists(mysqldiff_py)}\n', append=True)

            # 使用线程异步执行命令
            def execute_command():
                try:
                    if show_detailed_log:
                        window['-OUTPUT-'].update(f'[调试] 开始执行命令\n', append=True)
                    result = run_command_async(cmd, script_working_dir, show_detailed_log, None)
                    # 发送完成信号
                    window.write_event_value('-COMMAND_DONE-', result)
                except Exception as e:
                    window.write_event_value('-COMMAND_DONE-', e)

            # 启动执行线程
            thread = threading.Thread(target=execute_command, daemon=True)
            thread.start()
            window['-OUTPUT-'].update('[进度] 命令已提交后台执行，请稍候...\n', append=True)
            # 显示状态指示器
            window['-STATUS-'].update('正在执行中...', visible=True)
            window.refresh()

        # 处理命令执行完成事件
        if event == '-COMMAND_DONE-':
            # 隐藏状态指示器
            window['-STATUS-'].update(visible=False)
            
            # 重新启用按钮
            buttons = ['-RUN-', '-VIEW-', '-SAVE-', '-CLEAR-', '-EXIT-']
            for button in buttons:
                window[button].update(disabled=False)
            window.refresh()
            
            result = values['-COMMAND_DONE-']
            
            # 检查是否是异常
            if isinstance(result, Exception):
                window['-OUTPUT-'].update(f'[异常] 执行失败: {str(result)}\n', append=True)
                # 添加详细的错误信息和堆栈跟踪
                window['-OUTPUT-'].update(f'[异常详情] {traceback.format_exc()}\n', append=True)
                sg.popup_error(f'执行失败: {str(result)}\n\n详细信息已显示在输出窗口中。')
                continue

            output = result.stdout.strip() if result.stdout is not None else ""
            error = result.stderr.strip() if result.stderr is not None else ""
            returncode = result.returncode

            # --- 处理输出和结果 ---
            if show_detailed_log:
                window['-OUTPUT-'].update(f'[调试] 命令返回码: {returncode}\n', append=True)
                window['-OUTPUT-'].update(f'[调试] 输出长度: {len(output)} 字符\n', append=True)
                window['-OUTPUT-'].update(f'[调试] 错误输出长度: {len(error)} 字符\n', append=True)
            
            # 根据日志显示模式决定显示哪些内容
            filtered_output = ""
            filtered_error = ""
            
            if show_detailed_log:
                # 详细模式：显示所有内容
                filtered_output = output
                filtered_error = error
            else:
                # 简要模式：过滤掉调试信息，只显示关键信息
                if output:
                    lines = output.split('\n')
                    for line in lines:
                        # 只显示非调试信息和关键错误信息
                        if not line.startswith('[DEBUG]') and not line.startswith('DEBUG:') and not line.startswith('[调试]'):
                            filtered_output += line + '\n'
                
                if error:
                    lines = error.split('\n')
                    for line in lines:
                        # 显示所有错误信息
                        if line.strip():  # 只显示非空行
                            filtered_error += line + '\n'
            
            # 显示过滤后的内容
            if filtered_output or filtered_error or returncode != 0:
                if filtered_output:
                    window['-OUTPUT-'].update(f'{filtered_output}\n', append=True)
                if filtered_error:
                    window['-OUTPUT-'].update(f'[错误] {filtered_error}\n', append=True)
            else:
                window['-OUTPUT-'].update('[信息] 命令执行完成，无输出\n', append=True)
            
            # 确保在所有情况下都更新last_generated_sql_file变量
            if 'full_outfile_path_abs' in locals():
                last_generated_sql_file = full_outfile_path_abs
            
            if returncode != 0:
                window['-OUTPUT-'].update(f'[错误] 命令执行失败 (返回码: {returncode})\n', append=True)
                # 根据错误类型提供更友好的弹窗提示
                error_title = "执行出错"
                error_message = f'mysqldiff 执行出错，请查看输出！\n返回码: {returncode}'
                
                if '[ERROR] 用户名或密码错误' in error or '[ERROR] 用户名或密码错误' in output:
                    error_message = "数据库连接失败\n请检查用户名和密码是否正确"
                elif '[ERROR] 无法连接到数据库服务器' in error or '[ERROR] 无法连接到数据库服务器' in output:
                    error_message = "无法连接到数据库\n请检查主机地址和端口是否正确"
                elif '[ERROR] 数据库不存在' in error or '[ERROR] 数据库不存在' in output:
                    error_message = "数据库不存在\n请检查数据库名称是否正确"
                elif '[ERROR] 解析数据库映射失败' in error or '[ERROR] 解析数据库映射失败' in output:
                    error_message = "数据库映射参数格式错误\n请检查输入格式是否正确"
                elif '[ERROR] 数据库连接参数不完整' in error or '[ERROR] 数据库连接参数不完整' in output:
                    error_message = "数据库连接参数不完整\n请检查连接字符串格式"
                
                sg.popup_error(error_message, title=error_title)
            elif returncode == 0:
                # 成功执行时显示简要信息
                if show_detailed_log:
                    window['-OUTPUT-'].update(f'[完成] 差异已生成: {sql_file}\n', append=True)
                else:
                    window['-OUTPUT-'].update(f'差异已生成: {sql_file}\n', append=True)
                window['-OUTPUT-'].update(f'生成的SQL文件包含表结构差异和数据迁移语句\n', append=True)
                window['-OUTPUT-'].update(f'请检查文件内容并根据需要执行\n', append=True)

        # --- 查看 SQL 事件 ---
        if event == '-VIEW-':
            # 首先尝试使用最后生成的SQL文件路径（如果有的话）
            if last_generated_sql_file and os.path.exists(last_generated_sql_file):
                sql_file_to_view = last_generated_sql_file
                window['-OUTPUT-'].update(f"[调试 - VIEW] 使用最后生成的文件路径: '{sql_file_to_view}'\n", append=True)
            else:
                # 读取并清理输入框的值
                raw_outfile_input = values['-OUTFILE-']
                current_outfile_input = clean_input_string(raw_outfile_input)
                
                # 统一使用脚本目录作为基础路径
                base_dir = get_script_dir()
                # 如果输入的是相对路径，将其解析为相对于脚本目录的路径
                if not os.path.isabs(current_outfile_input):
                    sql_file_to_view = os.path.join(base_dir, current_outfile_input)
                else:
                    sql_file_to_view = current_outfile_input

                # 规范化路径，确保使用正斜杠
                sql_file_to_view = os.path.normpath(sql_file_to_view).replace('\\', '/').replace('\\', '/')

                # 添加调试信息到输出窗口
                window['-OUTPUT-'].update(f"\n[调试 - VIEW] 原始输入框值: '{raw_outfile_input}'\n", append=True)
                window['-OUTPUT-'].update(f"[调试 - VIEW] 清理后输入值: '{current_outfile_input}'\n", append=True)
                window['-OUTPUT-'].update(f"[调试 - VIEW] 计算出的路径: '{sql_file_to_view}'\n", append=True)
                window['-OUTPUT-'].update(f"[调试 - VIEW] 路径是否存在: {os.path.exists(sql_file_to_view)}\n", append=True)

                # 如果文件不存在，尝试使用脚本目录+文件名
                if not os.path.exists(sql_file_to_view):
                    # 尝试使用脚本目录+文件名
                    script_dir = get_script_dir()
                    filename = os.path.basename(current_outfile_input)
                    sql_file_to_view = os.path.join(script_dir, filename).replace('\\', '/').replace('\\', '/')
                    
                    # 如果之前生成过SQL文件，尝试使用之前记录的路径
                    if not os.path.exists(sql_file_to_view) and last_generated_sql_file:
                        sql_file_to_view = last_generated_sql_file
                    
                    window['-OUTPUT-'].update(f"[调试 - VIEW] 尝试使用脚本目录: '{sql_file_to_view}'\n", append=True)
                    window['-OUTPUT-'].update(f"[调试 - VIEW] 路径是否存在: {os.path.exists(sql_file_to_view)}\n", append=True)
                    
                    # 如果仍然不存在，显示错误信息
                    if not os.path.exists(sql_file_to_view):
                        # 添加更多调试信息
                        window['-OUTPUT-'].update(f"[调试 - VIEW] 当前工作目录: {os.getcwd()}\n", append=True)
                        window['-OUTPUT-'].update(f"[调试 - VIEW] 脚本目录: {get_script_dir()}\n", append=True)
                        if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
                            window['-OUTPUT-'].update(f'[调试 - VIEW] _MEIPASS: {getattr(sys, "_MEIPASS", "N/A")}\n', append=True)
                        
                        # 列出脚本目录下的所有sql文件
                        try:
                            script_dir = get_script_dir()
                            sql_files = [f for f in os.listdir(script_dir) if f.endswith('.sql')]
                            window['-OUTPUT-'].update(f"[调试 - VIEW] 脚本目录下的SQL文件: {sql_files}\n", append=True)
                        except Exception as e:
                            window['-OUTPUT-'].update(f"[调试 - VIEW] 列出文件时出错: {e}\n", append=True)
                        
                        sg.popup('⚠️ 文件不存在', f'文件未找到:\n{sql_file_to_view}', title='错误')
                        continue
            try:
                with open(sql_file_to_view, 'r', encoding='utf-8') as f:
                    content = f.read()
                view_layout = [
                    [sg.Text(f'SQL 内容: {sql_file_to_view}', text_color='blue')],
                    [sg.Multiline(content, size=(80, 20), disabled=True, key='-SQL_CONTENT-')],
                    [sg.Button('复制到剪贴板', key='-COPY_CLIPBOARD-'), sg.Button('关闭')]
                ]
                # 使用 modal=True 防止意外的新主窗口 (如果版本不支持，可以移除)
                view_window = sg.Window('查看 SQL', view_layout, modal=True)
                while True:
                    ve, _ = view_window.read()
                    if ve in (sg.WIN_CLOSED, '关闭'):
                        break
                    if ve == '-COPY_CLIPBOARD-':
                        try:
                            import pyperclip
                            pyperclip.copy(content)
                            sg.popup_quick_message(' 已复制到剪贴板', auto_close=True, auto_close_duration=1, keep_on_top=True)
                        except ImportError:
                            try:
                                import tkinter as tk
                                r = tk.Tk()
                                r.withdraw()
                                r.clipboard_clear()
                                r.clipboard_append(content)
                                r.update()
                                r.destroy()
                                sg.popup_quick_message(' 已复制到剪贴板', auto_close=True, auto_close_duration=1, keep_on_top=True)
                            except Exception as e:
                                sg.popup_error(f"复制到剪贴板失败: {e}\n请安装 'pyperclip' 模块 (pip install pyperclip)", keep_on_top=True)
                view_window.close()
            except Exception as e:
                sg.popup_error(f'无法读取文件: {str(e)}')

        # --- 保存 SQL 事件 ---
        if event == '-SAVE-':
            # 读取并清理输入框的值
            raw_outfile_input_save = values['-OUTFILE-']
            current_outfile_input_save = clean_input_string(raw_outfile_input_save)
            
            # 使用统一的路径处理逻辑
            script_dir = get_script_dir()
            if os.path.isabs(current_outfile_input_save):
                # 如果用户指定了绝对路径，检查是否在脚本目录下
                if current_outfile_input_save.startswith(script_dir.replace('\\', '/').replace('/', '/')):
                    source_sql_file = current_outfile_input_save
                else:
                    # 如果不在脚本目录下，尝试在脚本目录中查找同名文件
                    filename = os.path.basename(current_outfile_input_save)
                    source_sql_file = os.path.join(script_dir, filename).replace('\\', '/')
            else:
                # 使用相对路径时，默认在脚本目录中查找
                source_sql_file = os.path.join(script_dir, current_outfile_input_save).replace('\\', '/')

            # 添加调试信息到输出窗口
            window['-OUTPUT-'].update(f"\n[调试 - SAVE] 原始输入框值: '{raw_outfile_input_save}'\n", append=True)
            window['-OUTPUT-'].update(f"[调试 - SAVE] 清理后输入值: '{current_outfile_input_save}'\n", append=True)
            window['-OUTPUT-'].update(f"[调试 - SAVE] 计算出的源路径: '{source_sql_file}'\n", append=True)
            window['-OUTPUT-'].update(f"[调试 - SAVE] 路径是否存在: {os.path.exists(source_sql_file)}\n", append=True)

            if not os.path.exists(source_sql_file):
                sg.popup('⚠️ 源文件不存在，无法保存！', f'尝试查找: {source_sql_file}', title='错误')
                continue

            outfile_name_for_save = os.path.basename(source_sql_file)
            script_dir = get_script_dir()

            save_layout = [
                [sg.Text("保存为", font=("Microsoft YaHei", 12, "bold"))],
                [sg.Text("文件名:"), sg.Input(outfile_name_for_save, key='-FILENAME-', size=(40, 1), focus=True)],
                [sg.Text("保存到:"), sg.Input(script_dir, key='-FOLDER-', size=(40, 1)),
                 sg.FolderBrowse(initial_folder=script_dir)],
                [sg.Text("文件类型:"), sg.Combo([".sql", ".txt", "All Files"], default_value=".sql", key='-EXT-')],
                [sg.Button('保存', bind_return_key=True), sg.Button('取消')]
            ]

            save_window = sg.Window("另存为", save_layout, modal=True)

            while True:
                se, sv = save_window.read()
                if se in (sg.WIN_CLOSED, '取消'):
                    break
                if se == '保存':
                    filename = sv['-FILENAME-'].strip()
                    folder = sv['-FOLDER-'].strip()
                    ext = sv['-EXT-']
                    if not filename:
                        sg.popup_error("请输入文件名！")
                        continue
                    if ext == "All Files":
                        ext = ""
                    if not filename.lower().endswith(ext.lower()):
                        filename += ext

                    save_as = os.path.join(folder, filename)

                    if os.path.exists(save_as):
                        replace = sg.popup_yes_no(f"文件已存在，是否覆盖？\n{save_as}")
                        if replace != 'Yes':
                            continue

                    try:
                        content = ""
                        with open(source_sql_file, 'r', encoding='utf-8') as f:
                            content = f.read()

                        with open(save_as, 'w', encoding='utf-8') as f:
                            f.write(content)

                        sg.popup_quick_message(f'✅ 文件已保存至: {save_as}',
                                               background_color='green',
                                               font=('Microsoft YaHei', 10),
                                               auto_close=True,
                                               auto_close_duration=2)
                        break
                    except Exception as e:
                        sg.popup_error(f'保存失败: {str(e)}')

            save_window.close()

    window.close()


if __name__ == '__main__':
    main()